<template>
<div class="chat-container">
    <!-- 聊天头部 -->
    <header class="chat-header">
        <div class="header-left">
            <div class="back-btn" @click="goBack">
                <svg t="1759027300" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4163" width="24" height="24">
                    <path d="M671.968 912c-12.288 0-24.576-4.672-33.952-14.048L286.048 545.984c-18.752-18.72-18.752-49.12 0-67.872l351.968-352c18.752-18.752 49.12-18.752 67.872 0 18.752 18.72 18.752 49.12 0 67.872l-318.016 318.048 318.016 318.016c18.752 18.752 18.752 49.12 0 67.872C696.544 907.328 684.256 912 671.968 912z" p-id="4164" fill="#333333"></path>
                </svg>
            </div>
            <div class="user-info">
              <div @click="goUserProfile(talkerId)" class="avatar">
                <img v-if="talkerInfo.avatarUrl" :src="talkerInfo.avatarUrl" alt="" class="avatar-img" />
                <div v-else class="avatar-replace" style="background: #ff6b6b;">{{ talkerInfo.username?.charAt(0) }}</div>
              </div>
              <div class="username">{{ talkerInfo.username }}</div>
              <div class="connection-status" :class="{ connected: isTalkerOnline }">
                {{ isTalkerOnline ? '在线' : '离线' }}
              </div>
            </div>
        </div>
        <div class="header-right">
            <div class="more-btn">
                <svg t="1759027400" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5163" width="24" height="24">
                    <path d="M512 298.666667c35.413333 0 64-28.586667 64-64s-28.586667-64-64-64-64 28.586667-64 64 28.586667 64 64 64z m0 85.333333c-35.413333 0-64 28.586667-64 64s28.586667 64 64 64 64-28.586667 64-64-28.586667-64-64-64z m0 213.333333c-35.413333 0-64 28.586667-64 64s28.586667 64 64 64 64-28.586667 64-64-28.586667-64-64-64z" p-id="5164" fill="#333333"></path>
                </svg>
            </div>
        </div>
    </header>

    <!-- 聊天内容区域 -->
    <div class="chat-content" ref="chatContentRef" :style="showEmojiPanel ? 'padding-bottom: 260px;' : ''">
      <!-- 未读消息提示 -->
      <div v-if="showUnreadTip && conversation_Store.conversationInfo.unreadMessageCount > 0" 
        class="unread-tip" 
        @click="scrollToLastReadMessage"
      >
        <svg t="1760786369661" class="arrow-up" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4555" width="20" height="20">
          <path d="M533.333333 512L341.333333 704l29.866667 29.866667 162.133333-162.133334 162.133334 162.133334 29.866666-29.866667-192-192z m0-256L341.333333 448l29.866667 29.866667 162.133333-162.133334 162.133334 162.133334 29.866666-29.866667L533.333333 256z" 
            fill="#fff" p-id="4556">
          </path>
        </svg>
        <span class="unread-count">{{ conversation_Store.conversationInfo.unreadMessageCount }}条新消息</span>
      </div>
      
      <!-- 使用v-for循环渲染聊天消息 -->
      <div v-for="(message, index) in processedMessages" :key="message.id">
        <!-- 系统消息（时间或提示） -->
        <div v-if="message.showTime" class="chat-date">{{ message.formattedTime }}</div>

        <!-- 情况1.已经被撤回的消息 -->
        <div v-if="message.revoked" class="revoked-message-wrap">
          <div class="revoked-message">
            <span class="revoked-text">{{ message.senderId !== user_Store.userInfo.id ? '对方' : '你'}}撤回了一条消息&ensp;</span>
            <span v-if="message.canRecall" class="reedit-btn" @click="handleReedit(message)">重新编辑</span>
          </div>
        </div>
        
        <!-- 情况2.对方消息 -->
        <div v-else-if="message.senderId !== user_Store.userInfo.id" class="message-item friend-message" :data-message-id="message.id">
          <div class="avatar">
            <img v-if="talkerInfo.avatarUrl" :src="talkerInfo.avatarUrl" alt="" class="avatar-img" />
            <div v-else class="avatar-replace" style="background: #ff6b6b;">{{ talkerInfo.username?.charAt(0) }}</div>
          </div>
          <div class="message-bubble">
            <div v-if="message.messageType == 2" class="sticker-container">
              <div class="emoji-message">{{ message.content }}</div>
            </div>
            <div v-else class="message-text">{{ message.content }}</div>
          </div>
        </div>
        
        <!-- 情况3.我的消息 -->
        <div v-else-if="message.senderId === user_Store.userInfo.id" class="message-item my-message">
          <el-popover
            ref="popover"
            title=""
            trigger="contextmenu"
            content=""
            placement="bottom"
            :width="100"
            popper-style="min-width: 90px;"
          >
            <!-- 弹出框内容 -->
            <template #default>
              <div style="display: flex;justify-content: center;gap: 10px;">
                <div @click="copyMsg(message)" style="display: flex;flex-direction: column;align-items: center;cursor: pointer;">
                  <svg t="1760930777453" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4593" width="15" height="15">
                      <path d="M819.2 819.2v-68.266667h68.266667c40.96 0 68.266667-27.306667 68.266666-68.266666V136.533333c0-40.96-27.306667-68.266667-68.266666-68.266666H341.333333c-40.96 0-68.266667 27.306667-68.266666 68.266666v68.266667H204.8V136.533333c0-75.093333 61.44-136.533333 136.533333-136.533333h546.133334c75.093333 0 136.533333 61.44 136.533333 136.533333v546.133334c0 75.093333-61.44 136.533333-136.533333 136.533333h-68.266667z" 
                        fill="#777777" p-id="4594"></path>
                      <path d="M136.533333 204.8h546.133334c75.093333 0 136.533333 61.44 136.533333 136.533333v546.133334c0 75.093333-61.44 136.533333-136.533333 136.533333H136.533333c-75.093333 0-136.533333-61.44-136.533333-136.533333V341.333333c0-75.093333 61.44-136.533333 136.533333-136.533333z m0 68.266667c-40.96 0-68.266667 27.306667-68.266666 68.266666v546.133334c0 40.96 27.306667 68.266667 68.266666 68.266666h546.133334c40.96 0 68.266667-27.306667 68.266666-68.266666V341.333333c0-40.96-27.306667-68.266667-68.266666-68.266666H136.533333z" 
                        fill="#777777" p-id="4595"></path>
                      <path d="M238.933333 477.866667h341.333334c20.48 0 34.133333 13.653333 34.133333 34.133333s-13.653333 34.133333-34.133333 34.133333h-341.333334c-20.48 0-34.133333-13.653333-34.133333-34.133333s13.653333-34.133333 34.133333-34.133333zM238.933333 682.666667h341.333334c20.48 0 34.133333 13.653333 34.133333 34.133333s-13.653333 34.133333-34.133333 34.133333h-341.333334c-20.48 0-34.133333-13.653333-34.133333-34.133333s13.653333-34.133333 34.133333-34.133333z" 
                        fill="#777777" p-id="4596"></path>
                  </svg>
                  <el-button size="small" link>复制</el-button>
                </div>

                <div v-show="message.canRecall" @click="recallMsg(message)" style="display: flex;flex-direction: column;align-items: center;cursor: pointer;">
                  <svg t="1760931031961" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8702" width="15" height="15">
                    <path d="M642.346667 859.733333h-362.666667a32 32 0 0 1 0-64h362.666667a192 192 0 0 0 0-384H183.68a32 32 0 0 1 0-64h458.666667c141.376 0 256 114.624 256 256s-114.624 256-256 256z" 
                      fill="#777777" p-id="8703"></path>
                    <path d="M315.733333 589.013333a32.64 32.64 0 0 1-22.613333-9.386666l-177.066667-177.28a31.786667 31.786667 0 0 1 0-45.226667l177.066667-177.066667a32 32 0 1 1 45.226667 45.226667L183.893333 379.733333l154.453334 154.666667a32 32 0 0 1-22.613334 54.613333z" 
                      fill="#777777" p-id="8704"></path>
                  </svg>
                  <el-button size="small" link>撤回</el-button>
                </div>
              </div>
            </template>

            <!-- 外层展示 -->
            <template #reference>
              <!-- 消息泡泡 -->
              <div class="message-bubble" style="user-select: none;">
                <div v-if="message.messageType == 2" class="sticker-container">
                  <div class="emoji-message">{{ message.content }}</div>
                </div>
                <div v-else class="message-text">{{ message.content }}</div>
              </div>
            </template>
          </el-popover>
          <!-- 头像 -->
          <div class="avatar">
            <img v-if="user_Store.userInfo.avatarUrl" :src="user_Store.userInfo.avatarUrl" alt="" class="avatar-img" />
            <div v-else class="avatar-replace" style="background: #ff6b6b;">{{ user_Store.userInfo.username?.charAt(0) }}</div>
          </div>
        </div>

      </div>
    </div>

    <!-- 底部输入区域 -->
    <div class="chat-input-area" :class="{ 'emoji-visible': showEmojiPanel && isMobile }">
      <!--输入框-->
      <div class="input-box">
        <el-input ref="inputRef"  v-model="inputMessage" style="width: 100%" placeholder="请输入..."
          :rows="1" resize="none" :autosize="{ minRows: 1, maxRows: 5 }" type="textarea" 
          @keydown="handleKeyDown"
          @focus="handleInputFocus"
        />
      </div>

      <!--按钮操作-->
      <div class="input-actions">
        <!--表情面板按钮-->
        <div @click="showEmojiPanel = !showEmojiPanel" class="action-btn emoji-btn">
          <svg t="1759027600" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7163" width="24" height="24">
              <path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64z m0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z" p-id="7164" fill="#333333"></path>
              <path d="M512 140c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zM288 421a48.01 48.01 0 0 1 96 0 48.01 48.01 0 0 1-96 0z m224 272c-85.5 0-155.6-67.3-160-151.6-0.2-4.6 3.4-8.4 8-8.4h48.1c4.2 0 7.8 3.2 8.1 7.4C420 589.9 461.5 629 512 629s92.1-39.1 95.8-88.6c0.3-4.2 3.9-7.4 8.1-7.4H664c4.6 0 8.2 3.8 8 8.4-4.4 84.3-74.5 151.6-160 151.6z m184-272a48.01 48.01 0 0 1-96 0 48.01 48.01 0 0 1 96 0z" p-id="7165" fill="#333333"></path>
          </svg>
        </div>

        <!--更多操作按钮-->
        <div v-show="!inputMessage" class="action-btn more-actions-btn">
          <svg t="1759027700" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8163" width="24" height="24">
              <path d="M298.666667 512m-85.333334 0a85.333333 85.333333 0 1 0 170.666667 0 85.333333 85.333333 0 1 0-170.666667 0Z" p-id="8164" fill="#333333"></path>
              <path d="M512 512m-85.333333 0a85.333333 85.333333 0 1 0 170.666666 0 85.333333 85.333333 0 1 0-170.666666 0Z" p-id="8165" fill="#333333"></path>
              <path d="M725.333333 512m-85.333333 0a85.333333 85.333333 0 1 0 170.666667 0 85.333333 85.333333 0 1 0-170.666667 0Z" p-id="8166" fill="#333333"></path>
          </svg>
        </div>

        <!--发送按钮-->
        <div class="send-btn" v-show="inputMessage" @click="sendMessage">发送</div>
      </div>
    </div>

    <!-- 表情选择面板 -->
    <div class="emoji-panel" v-if="showEmojiPanel">
        <div class="emoji-tabs">
            <div class="emoji-tab"
                 v-for="tab in emojiTabs" 
                 :key="tab.key"
                 :class="{ active: activeEmojiTab === tab.key }" 
                 @click="switchEmojiTab(tab.key)">
              {{ tab.label }}
            </div>
        </div>
        <div class="emoji-list">
          <div class="emoji-item" 
               v-for="emoji in emojiData[activeEmojiTab]" 
               :key="emoji" 
               @click="insertEmoji(emoji)">
            {{ emoji }}
          </div>
        </div>
    </div>
</div>
</template>

<script lang="ts" setup>
import { ref, onMounted, onUnmounted, computed, nextTick, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import api from '@/api/index.js'
import path from "@/api/path";
import { getTimeInterval, formatDateTime } from '@/utils/format.js'
import { UserInfoStore } from '@/stores/userInfoStore'
import { TipStore } from '@/stores/tipStore'
import { ConversationStore } from '@/stores/conversationStore'
import { ChatUserInfoStore } from '@/stores/chatUserInfoStore'
import { useWebSocket } from '@/utils/useWebSocket'

const route = useRoute()
const router = useRouter()
const user_Store = UserInfoStore()
const conversation_Store = ConversationStore()
const chatUserInfo_Store = ChatUserInfoStore()
const tip_Store = TipStore()

// 移动端判断
const isMobile = computed(() =>
  /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
    navigator.userAgent
  )
)


// #region 通用数据
// 路由信息
const talkerId = computed(() => route.query.talkerId || 0)

// 引用
const inputRef = ref<HTMLTextAreaElement>(null) // 输入框
const chatContentRef = ref(null) // 聊天内容区域
const sender_id = user_Store.userInfo.id // 发送者id（即当前用户id）
const inputMessage = ref('') // 输入的消息

// 对方用户信息
const talkerInfo = ref({
  id: null,
  username: null,
  avatarUrl: null
})

// 是否为新会话
const isHasSessionInfo = computed(() => !conversation_Store.conversationInfo || !conversation_Store.conversationInfo.id)
// #endregion


// #region 通用方法
// 返回上一页
function goBack() {
  router.back()
}

// 跳转用户个人页
function goUserProfile(uid){
    router.push({
        name: 'UserProfile',
        query: { uid: uid }
    });
}

// 滚动到底部
function scrollToBottom() {
  if (chatContentRef.value) {
    const element = chatContentRef.value as HTMLElement
    if (isMobile.value) {
      element.scrollTo({
        top: element.scrollHeight,
        behavior: 'instant'
      })
    } else {
      // PC端直接设置scrollTop
      element.scrollTop = element.scrollHeight
    }
  }
}

// 复制消息
function copyMsg(msg) {
  try {
    navigator.clipboard.writeText(msg.content) // 使用现代浏览器的Clipboard API
  } catch (err) {
    console.error('Clipboard API不可用:', err)
    fallbackCopyToClipboard(msg.content) // 降级方案：使用传统的复制方法
  }
}

// 降级复制方案
function fallbackCopyToClipboard(text) {
  const textarea = document.createElement('textarea') // 创建一个临时的textarea元素
  textarea.value = text
  textarea.style.position = 'fixed' // 避免页面滚动
  textarea.style.opacity = '0'
  document.body.appendChild(textarea)
  textarea.focus()
  textarea.select()
  
  try {
    const successful = document.execCommand('copy')
    if (successful) {
      console.log('消息已复制到剪贴板')
    } else {
      console.error('复制失败')
    }
  } catch (err) {
    console.error('复制失败:', err)
  }
  document.body.removeChild(textarea) // 移除临时元素
}

// 处理键盘事件
function handleKeyDown(event: KeyboardEvent) {
  if (isMobile.value) return // 移动端不处理
  if (event.key === 'Enter') {
    if (event.shiftKey) {
      // Shift+Enter 换行
      event.preventDefault()
      const textareaElement = event.target as HTMLTextAreaElement
      const start = textareaElement.selectionStart
      const end = textareaElement.selectionEnd
      const text = inputMessage.value
      
      // 在光标位置插入换行
      inputMessage.value = text.substring(0, start) + '\n' + text.substring(end)
      
      // 重新设置光标位置
      nextTick(() => {
        textareaElement.focus()
        textareaElement.setSelectionRange(start + 1, start + 1) // 换行了，要移动光标到下一行开头
      })
    } else {
      // Enter 发送消息
      event.preventDefault()
      sendMessage()
    }
  }
}

// 处理输入框聚焦事件
function handleInputFocus() {
  if(showEmojiPanel.value){ return } // 输入框聚焦时，如果表情面板已打开，则不处理
  if (isMobile.value) {
    // 移动端延迟滚动
    setTimeout(() => {
      scrollToBottom()
    }, 300) // 延迟滚动，等待软键盘完全弹出
  } else {
    // PC端直接滚动
    scrollToBottom()
  }
}
// #endregion


// #region 聊天记录相关
const pageNum = ref(1)
const pageSize = ref(50)
const chatMessages = ref([]) // 聊天消息列表

// 添加计算属性，用来代替 chatMessages 进行数据的渲染
// 如果使用 chatMessages 渲染数据，想额外渲染时间间隔，要在html模版中嵌入函数，而当输入框绑定的值改变时，会触发Vue的响应式更新，导致模板中的所有函数重新执行
const processedMessages = computed(() => {
  // 只会在 chatMessages 变化时执行一次
  return chatMessages.value.map((msg, index) => {
    let showTime = false; // 是否显示时间间隔
    let formattedTime = ''; // 格式化后的时间
    let canRecall = false; // 该消息是否可以被撤回（2分钟内）

    if(msg.senderId == sender_id){
      canRecall = isWithinTwoMinutes(msg.createTime);
    }

    if ('isEarliestMsg' in msg) {
      showTime = true;
      formattedTime = formatTime_chat(msg.createTime);
    } else if (index > 0) {
      const prevMessage = chatMessages.value[index - 1];
      if (getTimeInterval(prevMessage.createTime, msg.createTime) > 5) {
        showTime = true;
        formattedTime = formatTime_chat(msg.createTime);
      }
    }
    
    return {
      ...msg,
      showTime,
      formattedTime,
      canRecall
    };
  });
});


// 判断消息是否在2分钟内（用于显示重新编辑按钮）
function isWithinTwoMinutes(createTime: string | Date) {
  if (!createTime) return false
  const msgDate = new Date(createTime)
  const now = new Date()
  const diffInMinutes = (now.getTime() - msgDate.getTime()) / (1000 * 60)
  return diffInMinutes < 2
}

// 格式化时间
function formatTime_chat(timeString) {
    // 1. 解析时间字符串（兼容不同格式，并处理时区）
    const targetDate = new Date(timeString);
    if (isNaN(targetDate.getTime())) {
        throw new Error('无效的时间字符串');
    }
    const now = new Date(); // 获取当前时间

    // 2. 根据时间差返回对应的时间格式
    // 如果是今天的
    if (targetDate.toDateString() === now.toDateString()) {
        return `${String(targetDate.getHours()).padStart(2, '0')}:${String(targetDate.getMinutes()).padStart(2, '0')}`;
    }
    // 如果是昨天的
    else if (targetDate.toDateString() === new Date(now.getTime() - 24 * 60 * 60 * 1000).toDateString()) {
        return `昨天 ${String(targetDate.getHours()).padStart(2, '0')}:${String(targetDate.getMinutes()).padStart(2, '0')}`;
    }
    // 在本年内的都不用返回年
    else if (targetDate.getFullYear() === now.getFullYear()) {
        const month = String(targetDate.getMonth() + 1).padStart(2, '0');
        const day = String(targetDate.getDate()).padStart(2, '0');
        return `${month}月${day}日 ${String(targetDate.getHours()).padStart(2, '0')}:${String(targetDate.getMinutes()).padStart(2, '0')}`;
    }
    else {
        const year = targetDate.getFullYear();
        const month = String(targetDate.getMonth() + 1).padStart(2, '0');
        const day = String(targetDate.getDate()).padStart(2, '0');
        return `${year}年${month}月${day}日 ${targetDate.getHours()}:${String(targetDate.getMinutes()).padStart(2, '0')}`;
    }
}
// #endregion


// #region 表情相关
const showEmojiPanel = ref(false) // 表情面板是否可见
const activeEmojiTab = ref('common') // 当前激活的表情标签页

// 表情数据
const emojiData = {
  common: ['😊', '😂', '😍', '🥰', '😘', '😭', '😅', '🤔', '😒', '🙄', '😤', '😡', '👍', '👎', '👏', '🙏', '👋', '🎉', '❤️', '💔'],
  smile: ['😀', '😃', '😄', '😆', '😉', '😋', '😎', '🤗', '😐', '😑', '😶', '😌', '😴', '😪', '😵', '🤐', '🤢', '🤧', '😇', '🤠'],
  animal: ['🐱', '🐶', '🐭', '🐹', '🐰', '🦊', '🐻', '🐼', '🐨', '🐯', '🦁', '🐮', '🐷', '🐸', '🐵', '🙈', '🙉', '🙊', '🐦', '🐧'],
  food: ['🍎', '🍌', '🍓', '🍇', '🍉', '🍑', '🍒', '🥝', '🍕', '🍔', '🌭', '🥪', '🌮', '🍜', '🍝', '🍰', '🍦', '🍩', '🍪', '🍺'],
  activity: ['⚽', '🏀', '🏈', '⚾', '🎾', '🏐', '🏓', '🏸', '🎮', '🎲', '🎸', '🎺', '🎻', '🎹', '🎤', '🎧', '🎬', '🎨', '🎯', '🎪']
}

// 标签页配置
const emojiTabs = [
  { key: 'common', label: '常用' },
  { key: 'smile', label: '表情' },
  { key: 'animal', label: '动物' },
  { key: 'food', label: '食物' },
  { key: 'activity', label: '活动' }
]


// 处理点击外部区域事件
function handleClickOutside(event: MouseEvent) {
  if (showEmojiPanel.value) {
    const emojiPanel = document.querySelector('.emoji-panel')
    const emojiBtn = document.querySelector('.emoji-btn')
    
    // 如果点击的不是表情面板且不是表情按钮，则关闭面板
    if (emojiPanel && !emojiPanel.contains(event.target as Node) && 
        emojiBtn && !emojiBtn.contains(event.target as Node)) {
      showEmojiPanel.value = false
      activeEmojiTab.value = 'common'
    }
  }
}

// 插入表情到输入框
function insertEmoji(emoji: string) {
  const textareaElement = document.querySelector('.input-box textarea') as HTMLTextAreaElement
  if (textareaElement) {
    const start = textareaElement.selectionStart
    const end = textareaElement.selectionEnd
    const text = inputMessage.value
    
    // 在光标位置插入表情
    inputMessage.value = text.substring(0, start) + emoji + text.substring(end)
    
    // 重新聚焦并设置光标位置
    nextTick(() => {
      textareaElement.focus()
      textareaElement.setSelectionRange(start + emoji.length, start + emoji.length) // 设置光标位置，不设的话会自动移动到末尾
    })
  } else {
    // 降级处理：如果无法获取元素，直接在末尾添加
    inputMessage.value += emoji
  }
}

// 切换表情标签页
function switchEmojiTab(tabKey: string) {
  activeEmojiTab.value = tabKey
  inputRef.value?.focus() // 切换标签页后重新聚焦到输入框
}

// #endregion


// #region 未读消息相关
const lastReadMsgId = ref(null) // 上次退出前最后阅读到的消息id
const showUnreadTip = ref(false) // 是否显示未读消息提示


// 获取上次阅读的消息id
const getLastReadMsgId = async () => {
  if(conversation_Store.conversationInfo?.id == null) return
  const res = await api.Chat_GetUserLastReadMsgId(conversation_Store.conversationInfo.id)
  lastReadMsgId.value = res.data.data
}


// 滚动到指定消息ID的位置
function scrollToMessage(messageId) {
  if (!chatContentRef.value || !messageId) return
  
  const element = chatContentRef.value as HTMLElement
  const messageElement = document.querySelector(`[data-message-id="${messageId}"]`)
  
  if (messageElement) {
    const messageRect = messageElement.getBoundingClientRect()
    const scrollTop = messageRect.top + element.scrollTop - 100
    
    // 立即滚动
    element.scrollTo({
      top: scrollTop,
      behavior: 'instant'
    })
  }
}

// 滚动到上次阅读的消息
function scrollToLastReadMessage() {
  if (lastReadMsgId.value) {
    scrollToMessage(lastReadMsgId.value)
    showUnreadTip.value = false // 隐藏提示
  }
}

// 检测是否需要显示未读消息提示
const checkUnreadMessages = () => {
  if(conversation_Store.conversationInfo?.unreadMessageCount > 99) return // 超过99条未读消息，则不显示提示
  if(conversation_Store.conversationInfo?.unreadMessageCount <=0 || !lastReadMsgId.value) return

  const hasLastReadMessage = chatMessages.value.some(msg => msg.id === lastReadMsgId.value)  // 检查最后阅读的消息是否在当前的聊天记录中
  if (hasLastReadMessage) {
    // 检查上次阅读的消息是否在当前视口中
    const messageElement = document.querySelector(`[data-message-id="${lastReadMsgId.value}"]`)
    if (messageElement && chatContentRef.value) {
      const chatRect = chatContentRef.value.getBoundingClientRect()
      const messageRect = messageElement.getBoundingClientRect()
      
      // 判断消息是否在当前视口中
      const isMessageInViewport = (
        messageRect.top >= chatRect.top && messageRect.bottom <= chatRect.bottom &&
        messageRect.left >= chatRect.left && messageRect.right <= chatRect.right
      )
      
      // 只有当消息不在当前视口中时才显示跳转提示
      if (!isMessageInViewport) {
        showUnreadTip.value = true
      }
    } else {
      // 如果无法获取元素位置信息，默认显示提示
      showUnreadTip.value = true
    }
  }
}
// #endregion


// #region WebSocket相关
// WebSocket配置
const wsUrl = computed(() => `ws://${path.devUrl}/ws/privateChat/${user_Store.userInfo.id}`)
const { isConnected, messages: wsMessages, sendPrivate, close: closeWebSocket } = useWebSocket(
  user_Store.userInfo.id, 
  talkerId.value, 
  wsUrl.value
)
const isTalkerOnline = ref(false) // 对方用户是否在线


// 处理WebSocket消息变化
const handleWebSocketMessage = async(newMessage) => {
  // 只处理当前聊天对象的消息
  if (newMessage.senderId == talkerId.value || newMessage.receiverId == talkerId.value) {
    // 检查是否是撤回消息
    if (newMessage.revoked == true) {
      // 找到对应的消息并标记为已撤回
      const messageToRevoke = chatMessages.value.find(msg => msg.id == newMessage.id)
      if (messageToRevoke) {
        messageToRevoke.revoked = true
      }
    } else {
      // 检查消息是否已存在（避免重复）
      const exists = chatMessages.value.some(msg =>  
        msg.id === newMessage.id &&
        msg.createTime === newMessage.createTime &&
        msg.senderId === newMessage.senderId
      )
      
      if (!exists) {
        chatMessages.value.push(newMessage) // 1.添加消息
        updateUserLastReadMsgId() // 2.更新用户对于该会话的最后一条阅读消息

        nextTick(() => {
          scrollToBottom()
        })
      }

    }
  }
}

// 监听WebSocket消息变化
watch(wsMessages, (newMessages) => {
  if (newMessages.length > 0) {
    const latestMessage = newMessages[newMessages.length - 1]
    handleWebSocketMessage(latestMessage)
  }
}, { deep: true })

// #endregion


// #region 前后端交互
// 发送（文本）消息
async function sendMessage() {
  if (talkerInfo.value.id == null) { 
    alert('无效的聊天会话');
    return
  }
  if (!inputMessage.value.trim()) return
  
  // 创建新消息
  const newMessage = {
    id: null,
    conversationId: conversation_Store.conversationInfo?.id ?? null,
    senderId: sender_id,
    receiverId: talkerId.value,
    revoked: false,
    content: inputMessage.value.trim(),
    messageType: 1, // 目前只支持发送文本消息
    status: 1,
    createTime: formatDateTime(new Date()),
  }

  // 1.发送消息到后端
  let addRes = await api.Chat_SendNewMsg(newMessage)
  if(addRes.data.code != 1) { return  }
  newMessage.id = addRes.data.data // 设置消息id

  // 如果是新会话，则获取并设置会话信息
  if (isHasSessionInfo.value) {
    const res = await api.Sessions_GetWhisper(talkerInfo.value.id)
    conversation_Store.conversationInfo = res.data.data
  }

  // 2.添加到消息列表
  chatMessages.value.push(newMessage) 
  if(chatMessages.value.length == 1){
    chatMessages.value[0].isEarliestMsg = true // 消息为该会话的第一条消息
  }

  // 3.使用WebSocket发送消息
  sendPrivate(newMessage)

  // 4.更新用户对于该会话的最后一条阅读消息
  updateUserLastReadMsgId()
  
  inputMessage.value = '' // 清空输入框
  showEmojiPanel.value = false // 关闭表情面板
  activeEmojiTab.value = 'common' // 重置表情标签页
  nextTick(() => {
    inputRef.value.focus() // 重新聚焦到输入框
  })
}

// 撤回消息
const recallMsg = async (msg) => {
  if(!msg || msg.revoked==true || !msg.id){
    tip_Store.warningTip("无效的操作")
    return
  }
  
  await api.Chat_RevokeMsg(msg.id).then(res => {
    if(res.data.code == 1){
      // 找到这条消息，将这条消息的revoked属性设置为true
      chatMessages.value.find(item => item.id === msg.id).revoked = true
      tip_Store.successTip()
    }else{
      tip_Store.errorTip('撤回失败，请稍后再试')
    }
  })

  // 实时撤回消息
  msg.revoked = true
  sendPrivate(msg)
}

// 获取会话中的聊天信息
const getChatMessages = async () => {
  const res = await api.Chat_GetByPage(talkerId.value, pageNum.value, pageSize.value) // 获得的数组：[m3,m2,m1]，初始数组：[m4,m5,m6]
  const list =  res.data.data.list

  // 将数组反转，因为聊天记录是按时间顺序倒序排列的，最新的在最前面，显示的时候要反过来
  list.reverse() // [m1,m2,m3]

  chatMessages.value = [...list, ...chatMessages.value] // [m1,m2,m3,m4,m5,m6]

  // 没有更多数据了，代表是最后一页
  if(res.data.data.total < pageSize.value && chatMessages.value.length > 0){
    chatMessages.value[0].isEarliestMsg = true // 给最早的一条消息加上一个字段，用于标识它是最早的消息，便于展示时间
  }
}

// 重新编辑撤回的消息
const handleReedit = (message) => {
  if (!message || !message.revoked) return
  
  // 将撤回的消息内容恢复到输入框
  if (message.content) {
    inputMessage.value = message.content
    nextTick(() => {
      inputRef.value?.focus()
    })
  }
}

// 获取对方信息
const getTalkerInfo = async () => {
  // 如果对方用户信息已存在于pinia仓库，则直接返回 仓库中的值
  const existingUser = chatUserInfo_Store.getUserById(talkerId.value);
  if (existingUser) {
    talkerInfo.value = existingUser;
    return;
  }

  const res = await api.User_GetById_ConvListQuery([talkerId.value])
  if(res.data?.data.length == 0) return // 对方用户账号不存在
  talkerInfo.value = res.data.data[0]
 
  chatUserInfo_Store.addOrUpdateUser(talkerInfo.value);  // 将获取到的用户信息存储到pinia仓库中
}

// 获取会话信息
const getConversationInfo = async () => {
  if(isHasSessionInfo.value){
    const res = await api.Sessions_GetWhisper(talkerInfo.value.id)
    if(res.data.data == null) return
    conversation_Store.conversationInfo = res.data.data
  }
}

// 获取对方用户在线状态
const getUserOnlineStatus = async () => {
  const res = await api.Chat_IsUserOnline(talkerInfo.value.id)
  isTalkerOnline.value = res.data.data.online
}

// 更新用户对于该会话的最后一条阅读消息
const updateUserLastReadMsgId = async () => {
  let lastId_store = conversation_Store.conversationInfo.lastMessageId
  if(conversation_Store.conversationInfo.id == null || chatMessages.value.length == 0) return
  if(lastReadMsgId.value == lastId_store && lastId_store != null) return // 上次阅读消息与当前最新消息一致，则不更新
  await api.Sessions_UpdateUserLastReadMsgId(conversation_Store.conversationInfo.id, chatMessages.value[chatMessages.value.length - 1].id)
}
// #endregion


// 组件挂载时触发
onMounted(async () => {
  document.addEventListener('click', handleClickOutside) // 添加点击事件监听器，点击外部区域关闭表情面板

  // 1.获取对方信息
  await getTalkerInfo()
  
  if(talkerInfo.value.id != null){
    await getConversationInfo() //2.获取会话信息
    if(isHasSessionInfo.value){ return } // 无会话信息，为首次对话，不再往下获取信息

    getUserOnlineStatus() // 3.获取对方用户在线状态
    await getChatMessages() // 4.获取会话中的聊天信息
    scrollToBottom() // 滚动到最底部

    await getLastReadMsgId() // 5.获取上次阅读的消息id
    checkUnreadMessages() // 检测是否显示未读消息提示
    
    updateUserLastReadMsgId() // 6.更新用户对于该会话的最后一条阅读消息
  }

})

// 组件卸载时触发
onUnmounted(() => {
  document.removeEventListener('click', handleClickOutside)
  closeWebSocket() // 关闭WebSocket连接
  conversation_Store.clear() // 清空会话信息
})

</script>

<style lang="scss" scoped>
.chat-container {
  display: flex;
  flex-direction: column;
  height: 100vh;
  background-color: #f5f5f5;
}

/* 聊天头部 */
.chat-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 60px;
  background-color: #fff;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 15px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  z-index: 100;
}

.header-left {
  display: flex;
  align-items: center;
}

.back-btn {
  padding: 8px;
  margin-right: 10px;
  cursor: pointer;
}

.user-info {
  display: flex;
}

.username {
  font-size: 16px;
  font-weight: 500;
  color: #333;
  display: flex;
  align-items: center;
}

.connection-status {
  font-size: 12px;
  color: #999;
  margin-left: 8px;
  display: flex;
  align-items: center;
}

.connection-status.connected {
  color: #07c160;
}

.status {
  font-size: 12px;
  color: #999;
  margin-top: 2px;
  max-width: 250px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.header-right {
  display: flex;
  align-items: center;
}

.more-btn {
  padding: 8px;
  cursor: pointer;
}

/* 聊天内容区域 */
.chat-content {
  flex: 1;
  padding: 70px 15px 80px;
  overflow-y: auto;
  position: relative;
  scrollbar-width: none; /* 隐藏Firefox滚动条 */
  -ms-overflow-style: none; /* 隐藏IE滚动条 */
}

/* 未读消息提示 */
.unread-tip {
  position: fixed;
  top: 90px;
  right: 0;
  background-color: #07c160;
  color: white;
  padding: 8px 12px;
  border-radius: 20px 0 0 20px;
  font-size: 14px;
  display: flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  z-index: 999;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  transition: all 0.3s ease;
}

.unread-tip:hover {
  background-color: #06a050;
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}

.unread-count {
  font-weight: 500;
}

.arrow-up {
  transition: transform 0.3s ease;
}

.chat-date {
  text-align: center;
  font-size: 12px;
  color: #999;
  margin: 10px 0;
}

.message-item {
  display: flex;
  margin-bottom: 15px;
  align-items: flex-start;
}

.friend-message {
  justify-content: flex-start;
}

.my-message {
  justify-content: flex-end;
}

.avatar {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  overflow: hidden;
  background-color: #eee;
  margin: 0 10px;
}

.avatar-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.avatar-replace{
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-size: 16px;
}

.message-bubble {
  max-width: 70%;
  padding: 10px 15px;
  border-radius: 18px;
  position: relative;
}

.message-bubble:has(.sticker-container) {
  padding: 0;
  background: transparent;
}

.friend-message .message-bubble {
  background-color: #fff;
  border-top-left-radius: 4px;
}

.my-message .message-bubble {
  background-color: #95ec69;
  border-top-right-radius: 4px;
}

.message-text {
  font-size: 14px;
  line-height: 1.4;
  word-break: break-word;
  white-space: pre-wrap; // 保留文本中的空白字符（包括空格、制表符和换行符）
}

.sticker-container {
  width: 120px;
  height: 120px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  margin: 0;
}

.sticker-img {
  max-width: 100%;
  max-height: 100%;
}

.emoji-message {
  font-size: 40px;
  line-height: 1;
}

/* 撤回消息样式 */
.revoked-message-wrap{
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 15px;
  margin-bottom: 15px;
}

.revoked-message {
  text-align: center;
  font-size: 12px;
  color: #999;
  background-color: rgba(0, 0, 0, 0.05);
  border-radius: 10px;
  padding: 4px 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: auto;
}

.revoked-text {
  color: #666;
  font-size: 12px;
}

.reedit-btn {
  color: #07c160;
  font-size: 12px;
  cursor: pointer;
  padding: 2px 6px;
  border-radius: 4px;
  transition: all 0.2s ease;
}

.reedit-btn:hover {
  background-color: rgba(7, 193, 96, 0.1);
  color: #06a050;
}

.system-tip {
  text-align: center;
  font-size: 12px;
  color: #999;
  background-color: rgba(0, 0, 0, 0.05);
  border-radius: 10px;
  padding: 5px 10px;
  margin: 10px auto;
  max-width: 80%;
}

/* 底部输入区域 */
.chat-input-area {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  background-color: #f8f8f8;
  display: flex;
  align-items: flex-end;
  padding: 10px 15px;
  border-top: 1px solid #e5e5e5;
  z-index: 100;
  transition: all 0.2s ease;
}

.input-actions {
  display: flex;
  align-items: flex-end;
  height: 100%;
  gap: 4px;
}

.action-btn {
  padding: 6px 6px 4px 6px;
  cursor: pointer;
}

.emoji-btn{
  display: flex;
  align-items: center;
}

.more-actions-btn{
  display: flex;
  align-items: center;
}

.input-box {
  flex: 1;
  margin: 0 10px;
}

.input-box textarea {
  width: 100%;
  min-height: 36px;
  max-height: 100px;
  border: none;
  border-radius: 18px;
  background-color: #fff;
  padding: 8px 15px;
  font-size: 14px;
  outline: none;
  resize: none;
  overflow-y: auto;
  font-family: inherit;
}

.send-btn {
  background-color: #07c160;
  color: #fff;
  padding: 6px 12px;
  border-radius: 4px;
  font-size: 14px;
  cursor: pointer;
  align-self: flex-end;
}

/* #region 表情选择面板 */
.emoji-panel {
  position: fixed;
  bottom: 70px;
  left: 15px;
  right: 15px;
  height: 240px;
  background-color: #fff;
  border-radius: 12px;
  box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.15);
  z-index: 999;
  overflow: hidden;
  animation: emojiPanelSlideUp 0.2s ease-out;
}

/* 手机端表情面板样式 */
@media (max-width: 768px) {
  .emoji-panel {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    height: 200px; /* 调整后的面板高度 */
    border-radius: 0;
    box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
    z-index: 1000;
  }
  
  /* 当表情面板显示时，输入区域要上移 */
  .chat-input-area.emoji-visible {
    position: fixed;
    bottom: 200px; /* 上移的距离等于调整后表情面板高度 */
    transition: bottom 0.3s ease;
  }
}

@keyframes emojiPanelSlideUp {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.emoji-tabs {
  display: flex;
  height: 40px;
  background-color: #fafafa;
  border-bottom: 1px solid #f0f0f0;
}

.emoji-tab {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  color: #666;
  cursor: pointer;
  transition: all 0.2s ease;
  position: relative;
}

.emoji-tab:hover {
  background-color: #f5f5f5;
}

.emoji-tab.active {
  color: #07c160;
  font-weight: 500;
}

.emoji-tab.active::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 20%;
  right: 20%;
  height: 3px;
  background-color: #07c160;
  border-radius: 2px;
}

.emoji-list {
  display: flex;
  flex-wrap: wrap;
  padding: 15px;
  max-height: 170px;
  overflow-y: auto;
  gap: 3px;

  /* 滚动条样式 */
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none; /* IE and Edge */
  scroll-behavior: smooth; /* 平滑滚动 */
  -webkit-overflow-scrolling: touch; /* iOS惯性滚动 */
}

.emoji-list::-webkit-scrollbar {
  display: none; /* Chrome, Safari and Opera */
}

.emoji-item {
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 24px;
  cursor: pointer;
  border-radius: 8px;
  transition: all 0.2s ease;
  user-select: none;
}

.emoji-item:hover {
  background-color: #f0f0f0;
  transform: scale(1.1);
}

// :active 是 CSS 的一个伪类，表示元素被 激活 的状态，通常是在用户**点击（鼠标按下）**的瞬间触发
.emoji-item:active {
  transform: scale(0.95); // 轻微缩小，模拟按压效果
  background-color: #e0e0e0; // 背景色变深，提供视觉反馈
}
/* #endregion 表情选择面板结束 */


/* #region 覆盖样式 */
:deep(.input-box .el-textarea__inner:focus){
  box-shadow: 0 0 0 1px #ff6b6b inset;
}

/* 
  这样是无效的，Element Plus的popover组件使用了Vue3的 <Teleport> 功能，将popover内容渲染到body元素下，
  不在App.vue组件的div内，它和App.vue组件的div平级，因此，
  - ✅ popper-style 属性有效（直接应用到popover元素）
  - ❌ :deep() 选择器无效（scoped CSS无法穿透到body层级）
  - ❌ 组件内的CSS选择器无效（因为不在组件DOM树内）
*/
// :deep(.el-popover.el-popper){
//   min-width: 90px !important;
// }

/* #endregion 覆盖样式结束 */
 </style>
